Базовые типы данных
В этой главе мы изучим скалярные типы данных в Rust — простейшие типы, которые представляют одно значение. Rust является статически типизированным языком, что означает, что все типы переменных должны быть известны во время компиляции.
Статическая типизация в Rust
Явное указание типов
fn main() {
let x: i32 = 42; // 32-битное целое число
let y: f64 = 3.14159; // 64-битное число с плавающей точкой
let z: bool = true; // Булево значение
let c: char = '🦀'; // Символ Unicode
println!("x: {}, y: {}, z: {}, c: {}", x, y, z, c);
}
Автоматический вывод типов
fn main() {
let x = 42; // Компилятор выводит i32
let y = 3.14159; // Компилятор выводит f64
let z = true; // Компилятор выводит bool
let c = '🦀'; // Компилятор выводит char
// Можно проверить тип с помощью std::any::type_name
println!("Тип x: {}", std::any::type_name_of_val(&x));
println!("Тип y: {}", std::any::type_name_of_val(&y));
}
Rust имеет мощную систему вывода типов. В большинстве случаев вам не нужно явно указывать типы — компилятор выведет их автоматически на основе контекста использования.
Целые числа
Rust предоставляет несколько типов целых чисел разного размера:
Типы целых чисел
- Знаковые целые
- Беззнаковые целые
- Архитектурно-зависимые
| Тип | Размер | Диапазон значений |
|---|---|---|
i8 | 8 бит | -128 до 127 |
i16 | 16 бит | -32,768 до 32,767 |
i32 | 32 бита | -2,147,483,648 до 2,147,483,647 |
i64 | 64 бита | -9,223,372,036,854,775,808 до 9,223,372,036,854,775,807 |
i128 | 128 бит | -(2^127) до 2^127-1 |
isize | зависит от архитектуры | 32 или 64 бита |
fn main() {
let small: i8 = -128; // Минимальное значение для i8
let medium: i32 = -2_000_000; // i32 по умолчанию
let large: i64 = -9_223_372_036_854_775_808;
println!("i8: {}", small);
println!("i32: {}", medium);
println!("i64: {}", large);
// Проверка границ типов
println!("Минимальное i32: {}", i32::MIN);
println!("Максимальное i32: {}", i32::MAX);
}
| Тип | Размер | Диапазон значений |
|---|---|---|
u8 | 8 бит | 0 до 255 |
u16 | 16 бит | 0 до 65,535 |
u32 | 32 бита | 0 до 4,294,967,295 |
u64 | 64 бита | 0 до 18,446,744,073,709,551,615 |
u128 | 128 бит | 0 до 2^128-1 |
usize | зависит от архитектуры | 32 или 64 бита |
fn main() {
let byte: u8 = 255; // Максимальное значение для u8
let count: u32 = 4_294_967_295; // Максимальное значение для u32
let huge: u128 = 340_282_366_920_938_463_463_374_607_431_768_211_455;
println!("u8: {}", byte);
println!("u32: {}", count);
println!("u128: {}", huge);
// Проверка границ беззнаковых типов
println!("Максимальное u8: {}", u8::MAX);
println!("Минимальное u8: {}", u8::MIN); // Всегда 0
}
isize и usize — размер зависит от архитектуры процессора:
fn main() {
let index: usize = 42; // Для индексации массивов
let pointer_diff: isize = -10; // Для арифметики указателей
println!("usize: {}", index);
println!("isize: {}", pointer_diff);
// Информация о размере типов
println!("Размер usize: {} байт", std::mem::size_of::<usize>());
println!("Размер isize: {} байт", std::mem::size_of::<isize>());
// Границы значений
println!("usize::MAX: {}", usize::MAX);
println!("isize::MIN: {}", isize::MIN);
println!("isize::MAX: {}", isize::MAX);
}
Когда использовать:
usize— для размеров коллекций и индексацииisize— для арифметики указателей (редко в обычном коде)
Литералы целых чисел
fn main() {
// Десятичные числа
let decimal = 98_222;
// Шестнадцатеричные
let hex = 0xff;
// Восьмеричные
let octal = 0o77;
// Двоичные
let binary = 0b1111_0000;
// Байт (только u8)
let byte = b'A';
println!("Десятичное: {}", decimal);
println!("Шестнадцатеричное: {} ({})", hex, hex);
println!("Восьмеричное: {} ({})", octal, octal);
println!("Двоичное: {} ({})", binary, binary);
println!("Байт: {} (символ: {})", byte, byte as char);
// Суффиксы типов
let typed_num = 42u64; // Явно указываем тип u64
let another_typed = 123i8; // Явно указываем тип i8
println!("u64: {}", typed_num);
println!("i8: {}", another_typed);
}
Переполнение целых чисел
fn main() {
// В debug режиме - паника при переполнении
// В release режиме - "обёртывание" значения
let mut x: u8 = 255;
println!("x = {}", x);
// В release режиме: 255 + 1 = 0 (переполнение)
// В debug режиме: паника!
// x += 1; // Раскомментируйте для проверки
// Явное обёртывание - безопасно в любом режиме
x = x.wrapping_add(1);
println!("После wrapping_add(1): {}", x); // 0
x = x.wrapping_sub(1);
println!("После wrapping_sub(1): {}", x); // 255
// Проверяющие операции
if let Some(result) = 255u8.checked_add(1) {
println!("Результат: {}", result);
} else {
println!("Переполнение при сложении!");
}
// Насыщающие операции
let saturated = 255u8.saturating_add(10);
println!("Насыщающее сложение: {}", saturated); // 255
}
Числа с плавающей точкой
Rust поддерживает два типа чисел с плавающей точкой согласно стандарту IEEE-754:
Типы с плавающей точкой
f32 - одинарная точность
- Размер: 32 бита
- Точность: ~6-7 значащих цифр
- Диапазон: ±1.175494e-38 до ±3.402823e38
fn main() {
let x: f32 = 3.14159;
let y: f32 = 1e6; // Научная нотация
println!("f32: {}", x);
println!("Научная нотация: {}", y);
println!("f32::MAX: {}", f32::MAX);
println!("f32::MIN: {}", f32::MIN);
}
f64 - двойная точность
- Размер: 64 бита
- Точность: ~15-17 значащих цифр
- Диапазон: ±2.225074e-308 до ±1.797693e308
fn main() {
let x = 3.14159265358979323846; // f64 по умолчанию
let y: f64 = 2.71828182845904523536;
println!("π: {}", x);
println!("e: {}", y);
println!("f64::MAX: {}", f64::MAX);
println!("f64::MIN: {}", f64::MIN);
}
Операции с числами с плавающей точкой
- Базовые операции
- Математические функции
- Специальные значения
fn main() {
let x = 10.5;
let y = 3.2;
println!("Сложение: {} + {} = {}", x, y, x + y);
println!("Вычитание: {} - {} = {}", x, y, x - y);
println!("Умножение: {} * {} = {}", x, y, x * y);
println!("Деление: {} / {} = {}", x, y, x / y);
println!("Остаток: {} % {} = {}", x, y, x % y);
// Унарные операции
println!("Отрицание: -{} = {}", x, -x);
println!("Абсолютное значение: |{}| = {}", -x, (-x).abs());
}
fn main() {
let x = 2.0;
let y = 3.14159;
// Степенные функции
println!("x^2 = {}", x.powi(2)); // Целая степень
println!("x^y = {}", x.powf(y)); // Вещественная степень
println!("e^x = {}", x.exp()); // Экспонента
println!("2^x = {}", x.exp2()); // Степень двойки
// Корни и логарифмы
println!("√x = {}", x.sqrt()); // Квадратный корень
println!("∛x = {}", x.cbrt()); // Кубический корень
println!("ln(x) = {}", x.ln()); // Натуральный логарифм
println!("log₂(x) = {}", x.log2()); // Логарифм по основанию 2
println!("log₁₀(x) = {}", x.log10()); // Десятичный логарифм
// Тригонометрические функции
println!("sin(π) = {}", y.sin()); // Синус
println!("cos(π) = {}", y.cos()); // Косинус
println!("tan(π/4) = {}", (y/4.0).tan()); // Тангенс
// Округление
let z = 3.7;
println!("floor({}) = {}", z, z.floor()); // Округление вниз
println!("ceil({}) = {}", z, z.ceil()); // Округление вверх
println!("round({}) = {}", z, z.round()); // Математическое округление
println!("trunc({}) = {}", z, z.trunc()); // Отбрасывание дробной части
}
fn main() {
// Бесконечности
let positive_infinity = f64::INFINITY;
let negative_infinity = f64::NEG_INFINITY;
// "Не число" (Not a Number)
let not_a_number = f64::NAN;
println!("Положительная бесконечность: {}", positive_infinity);
println!("Отрицательная бесконечность: {}", negative_infinity);
println!("NaN: {}", not_a_number);
// Проверка специальных значений
println!("Является ли бесконечностью: {}", positive_infinity.is_infinite());
println!("Является ли NaN: {}", not_a_number.is_nan());
println!("Является ли обычным числом: {}", 42.0.is_normal());
// Операции с бесконечностью
println!("∞ + 1 = {}", positive_infinity + 1.0);
println!("∞ * 2 = {}", positive_infinity * 2.0);
println!("∞ / ∞ = {}", positive_infinity / positive_infinity); // NaN
// NaN не равно ничему, включая само себя!
println!("NaN == NaN: {}", not_a_number == not_a_number); // false!
println!("NaN.eq(&NaN): {}", not_a_number.eq(¬_a_number)); // false!
}
Проблемы точности с плавающей точкой
fn main() {
// Проблема точности
let x = 0.1 + 0.2;
println!("0.1 + 0.2 = {}", x); // Не равно 0.3!
println!("x == 0.3: {}", x == 0.3); // false
// Правильное сравнение чисел с плавающей точкой
let epsilon = 1e-10;
let difference = (x - 0.3).abs();
println!("Разность: {}", difference);
println!("Приблизительно равно 0.3: {}", difference < epsilon);
// Использование библиотеки для точного сравнения
fn approximately_equal(a: f64, b: f64) -> bool {
(a - b).abs() < f64::EPSILON * 2.0
}
println!("Приблизительно равны: {}", approximately_equal(x, 0.3));
// Демонстрация накопления ошибок
let mut sum = 0.0;
for _ in 0..10 {
sum += 0.1;
}
println!("10 * 0.1 = {}", sum);
println!("sum == 1.0: {}", sum == 1.0); // Может быть false
}
Булевы значения
Тип bool представляет логические значения истина/ложь:
Основы работы с bool
fn main() {
let is_rust_awesome = true;
let is_difficult: bool = false; // Явное указание типа
println!("Rust потрясающий: {}", is_rust_awesome);
println!("Rust сложный: {}", is_difficult);
// Логические операции
let and_result = is_rust_awesome && is_difficult; // И (AND)
let or_result = is_rust_awesome || is_difficult; // ИЛИ (OR)
let not_result = !is_difficult; // НЕ (NOT)
println!("true && false = {}", and_result); // false
println!("true || false = {}", or_result); // true
println!("!false = {}", not_result); // true
// Короткозамкнутые вычисления
let x = 5;
let result = x > 0 && x < 10; // Второе условие не проверяется, если первое false
println!("5 в диапазоне (0, 10): {}", result);
}
Преобразования и сравнения
- Операции сравнения
- Преобразования
- Pattern matching
fn main() {
let a = 5;
let b = 10;
println!("{} == {}: {}", a, b, a == b); // Равенство
println!("{} != {}: {}", a, b, a != b); // Неравенство
println!("{} < {}: {}", a, b, a < b); // Меньше
println!("{} > {}: {}", a, b, a > b); // Больше
println!("{} <= {}: {}", a, b, a <= b); // Меньше или равно
println!("{} >= {}: {}", a, b, a >= b); // Больше или равно
// Сравнение строк
let s1 = "hello";
let s2 = "world";
println!("{} == {}: {}", s1, s2, s1 == s2);
println!("{} < {} (лексикографически): {}", s1, s2, s1 < s2);
}
fn main() {
let flag = true;
// bool -> число
let as_u8 = flag as u8;
let as_i32 = flag as i32;
println!("true как u8: {}", as_u8); // 1
println!("true как i32: {}", as_i32); // 1
let false_flag = false;
println!("false как u8: {}", false_flag as u8); // 0
// Число -> bool (только явно, не автоматически!)
let num = 42;
let bool_from_num = num != 0; // Любое ненулевое число -> true
println!("42 != 0: {}", bool_from_num);
// Строка -> bool через сравнение
let text = "true";
let bool_from_string = text == "true";
println!("'{}' == 'true': {}", text, bool_from_string);
// Использование в условиях
let value = 0;
if value != 0 { // В Rust нужно явное сравнение
println!("Значение ненулевое");
} else {
println!("Значение равно нулю");
}
}
fn main() {
let condition = true;
// Сопоставление с образцом
match condition {
true => println!("Условие истинно"),
false => println!("Условие ложно"),
}
// Функция, возвращающая bool
fn is_even(n: i32) -> bool {
n % 2 == 0
}
let number = 42;
match is_even(number) {
true => println!("{} - чётное число", number),
false => println!("{} - нечётное число", number),
}
// Более сложные паттерны с булевыми значениями
let a = true;
let b = false;
match (a, b) {
(true, true) => println!("Оба true"),
(true, false) => println!("Первый true, второй false"),
(false, true) => println!("Первый false, второй true"),
(false, false) => println!("Оба false"),
}
}
Символы (char)
Тип char в Rust представляет Unicode-скаляр и занимает 4 байта:
Основы работы с символами
fn main() {
let c1 = 'z'; // Латинский символ
let c2 = 'ℤ'; // Математический символ
let c3 = '😻'; // Эмодзи
let c4 = '中'; // Китайский иероглиф
let c5 = '\u{1F980}'; // Краб через Unicode-код
println!("Символы: {} {} {} {} {}", c1, c2, c3, c4, c5);
// Размер char всегда 4 байта
println!("Размер char: {} байт", std::mem::size_of::<char>());
// Получение Unicode-кода символа
println!("Код символа 'A': {}", 'A' as u32);
println!("Код символа '🦀': {}", '🦀' as u32);
// Создание символа из кода
if let Some(symbol) = char::from_u32(65) {
println!("Символ с кодом 65: {}", symbol); // 'A'
}
if let Some(crab) = char::from_u32(129408) {
println!("Символ с кодом 129408: {}", crab); // '🦀'
}
}
Методы работы с символами
- Классификация символов
- Изменение регистра
- Escape-последовательности
fn main() {
let chars = ['A', 'a', '5', ' ', '\n', '!', '中', '🦀'];
for ch in chars {
println!("\nСимвол: '{}'", ch);
println!(" Буква: {}", ch.is_alphabetic());
println!(" Цифра: {}", ch.is_numeric());
println!(" Алфавитно-цифровой: {}", ch.is_alphanumeric());
println!(" Пробельный: {}", ch.is_whitespace());
println!(" Управляющий: {}", ch.is_control());
println!(" Верхний регистр: {}", ch.is_uppercase());
println!(" Нижний регистр: {}", ch.is_lowercase());
println!(" ASCII: {}", ch.is_ascii());
}
}
fn main() {
let chars = ['A', 'a', 'Ñ', 'ñ', 'Ё', 'ё', '中'];
for ch in chars {
println!("Исходный: '{}'", ch);
// Преобразование в верхний регистр
for upper in ch.to_uppercase() {
print!(" Верхний: '{}'", upper);
}
println!();
// Преобразование в нижний регистр
for lower in ch.to_lowercase() {
print!(" Нижний: '{}'", lower);
}
println!();
// Некоторые символы могут расширяться при преобразовании
// Например, немецкий ß в верхнем регистре становится SS
}
// Специальный случай: немецкий ß
println!("\nСпециальный случай:");
let sharp_s = 'ß';
println!("Исходный: '{}'", sharp_s);
for upper in sharp_s.to_uppercase() {
print!("Верхний: '{}'", upper);
}
println!(); // Выведет: SS
}
fn main() {
// Escape-последовательности
let newline = '\n'; // Новая строка
let tab = '\t'; // Табуляция
let carriage_return = '\r'; // Возврат каретки
let backslash = '\\'; // Обратная косая черта
let single_quote = '\''; // Одинарная кавычка
let null = '\0'; // Нулевой символ
println!("Новая строка:{}", newline);
println!("Табуляция:{}После табуляции", tab);
println!("Обратная косая черта: {}", backslash);
println!("Одинарная кавычка: {}", single_quote);
println!("Нулевой символ код: {}", null as u32);
// Unicode escape-последовательности
let unicode1 = '\u{41}'; // 'A' через 2-значный hex
let unicode2 = '\u{1F980}'; // '🦀' через многозначный hex
println!("Unicode A: {}", unicode1);
println!("Unicode краб: {}", unicode2);
// Байтовые escape-последовательности (только ASCII)
let byte_char = '\x41'; // 'A' через байтовый код
println!("Байтовый символ: {}", byte_char);
}
Преобразования между типами
Безопасные преобразования
fn main() {
// Расширяющие преобразования (безопасные)
let small: i8 = 42;
let medium: i16 = small as i16; // i8 -> i16
let large: i32 = medium as i32; // i16 -> i32
let huge: i64 = large as i64; // i32 -> i64
println!("i8: {} -> i16: {} -> i32: {} -> i64: {}",
small, medium, large, huge);
// Беззнаковые к знаковым большего размера
let unsigned: u8 = 255;
let signed: i16 = unsigned as i16; // u8 -> i16 безопасно
println!("u8: {} -> i16: {}", unsigned, signed);
// f32 -> f64 безопасно
let float32: f32 = 3.14159;
let float64: f64 = float32 as f64;
println!("f32: {} -> f64: {}", float32, float64);
}
Небезопасные преобразования
fn main() {
// Сужающие преобразования могут терять данные
let large: i32 = 300;
let small: i8 = large as i8; // Переполнение!
println!("i32: {} -> i8: {}", large, small); // 300 -> 44
// Знаковые в беззнаковые
let negative: i32 = -1;
let unsigned: u32 = negative as u32;
println!("i32: {} -> u32: {}", negative, unsigned); // -1 -> 4294967295
// Числа с плавающей точкой в целые
let float_val: f64 = 3.99;
let int_val: i32 = float_val as i32; // Отбрасывает дробную часть
println!("f64: {} -> i32: {}", float_val, int_val); // 3.99 -> 3
// Слишком большие значения
let huge_float: f64 = 1e20;
let overflow_int: i32 = huge_float as i32; // Неопределённое поведение
println!("f64: {} -> i32: {}", huge_float, overflow_int);
}
Проверяемые преобразования
fn main() {
use std::convert::TryInto;
let large: i32 = 300;
// Проверяемое преобразование
match large.try_into() {
Ok(small_val) => {
let small_val: i8 = small_val;
println!("Успешное преобразование: {}", small_val);
}
Err(_) => println!("Ошибка: {} не помещается в i8", large),
}
// Более короткая запись с unwrap_or
let safe_small: i8 = large.try_into().unwrap_or(0);
println!("Безопасное преобразование: {}", safe_small);
// Проверка границ вручную
if large >= i8::MIN as i32 && large <= i8::MAX as i32 {
let small = large as i8;
println!("Безопасное приведение: {}", small);
} else {
println!("Значение {} выходит за границы i8", large);
}
}
Составные литералы и суффиксы типов
Суффиксы типов
fn main() {
// Целые числа с суффиксами
let a = 42i32; // i32
let b = 42u64; // u64
let c = 42isize; // isize
let d = 42usize; // usize
// Числа с плавающей точкой с суффиксами
let e = 3.14f32; // f32
let f = 2.71828f64; // f64
println!("i32: {}, u64: {}, isize: {}, usize: {}", a, b, c, d);
println!("f32: {}, f64: {}", e, f);
// Суффиксы полезны при передаче в функции
fn process_u64(value: u64) {
println!("Обработка u64: {}", value);
}
fn process_f32(value: f32) {
println!("Обработка f32: {}", value);
}
process_u64(123u64); // Явно указываем тип
process_f32(3.14f32); // Явно указываем тип
// Без суффиксов компилятор может не понять тип
// process_u64(123); // Ошибка: неясен тип
process_u64(123_u64); // Альтернативный синтаксис
}
Разделители в числах
fn main() {
// Разделители подчёркивания для читаемости
let million = 1_000_000;
let binary_mask = 0b1111_0000_1010_1010;
let hex_color = 0xFF_EC_DE;
let float_precise = 1_234.567_890;
println!("Миллион: {}", million);
println!("Двоичная маска: {} ({})", binary_mask, binary_mask);
println!("Hex цвет: {} ({})", hex_color, hex_color);
println!("Точное число: {}", float_precise);
// Разделители можно использовать в любых местах
let weird_but_valid = 1_2_3_4_5_u32;
println!("Странное, но валидное: {}", weird_but_valid); // 12345
// Популярные паттерны разделителей
let bytes_in_gb = 1_073_741_824; // Гигабайт в байтах
let nanoseconds = 1_000_000_000; // Наносекунд в секунде
let big_number = 123_456_789_012_345_u64; // Большое число
println!("ГБ в байтах: {}", bytes_in_gb);
println!("Наносекунд в секунде: {}", nanoseconds);
println!("Большое число: {}", big_number);
}
Практические примеры
Калькулятор с разными типами
fn main() {
// Функции для работы с разными типами
fn add_integers(a: i32, b: i32) -> i32 {
a + b
}
fn add_floats(a: f64, b: f64) -> f64 {
a + b
}
fn is_even(n: i32) -> bool {
n % 2 == 0
}
fn first_letter(text: &str) -> Option<char> {
text.chars().next()
}
// Тестирование функций
let int_result = add_integers(42, 58);
let float_result = add_floats(3.14159, 2.71828);
let even_check = is_even(int_result);
let letter = first_letter("Rust");
println!("Сумма целых: {}", int_result);
println!("Сумма дробных: {}", float_result);
println!("Результат чётный: {}", even_check);
if let Some(ch) = letter {
println!("Первая буква: {}", ch);
}
// Демонстрация точности
let precise_calc = (int_result as f64) * float_result;
println!("Произведение: {}", precise_calc);
}
Анализатор текста
fn main() {
let text = "Hello, 世界! 🦀 Rust 2024";
let mut letter_count = 0;
let mut digit_count = 0;
let mut whitespace_count = 0;
let mut punctuation_count = 0;
let mut emoji_count = 0;
let mut other_count = 0;
println!("Анализ текста: '{}'", text);
for ch in text.chars() {
if ch.is_alphabetic() {
letter_count += 1;
} else if ch.is_numeric() {
digit_count += 1;
} else if ch.is_whitespace() {
whitespace_count += 1;
} else if ch.is_ascii_punctuation() {
punctuation_count += 1;
} else if (ch as u32) > 127 && !ch.is_alphabetic() && !ch.is_numeric() {
// Простая проверка на эмодзи и специальные символы
emoji_count += 1;
} else {
other_count += 1;
}
println!("'{}': код U+{:04X}, категория: {}",
ch,
ch as u32,
categorize_char(ch));
}
println!("\nСтатистика:");
println!("Букв: {}", letter_count);
println!("Цифр: {}", digit_count);
println!("Пробельных: {}", whitespace_count);
println!("Знаков препинания: {}", punctuation_count);
println!("Эмодзи и спец. символов: {}", emoji_count);
println!("Прочих: {}", other_count);
}
fn categorize_char(ch: char) -> &'static str {
if ch.is_ascii_alphabetic() {
"ASCII буква"
} else if ch.is_alphabetic() {
"Unicode буква"
} else if ch.is_ascii_digit() {
"ASCII цифра"
} else if ch.is_numeric() {
"Unicode цифра"
} else if ch.is_whitespace() {
"Пробел"
} else if ch.is_ascii_punctuation() {
"Пунктуация"
} else if ch.is_control() {
"Управляющий символ"
} else {
"Прочее"
}
}
Размеры типов в памяти
fn main() {
use std::mem;
println!("Размеры скалярных типов в байтах:");
println!("i8: {}", mem::size_of::<i8>());
println!("i16: {}", mem::size_of::<i16>());
println!("i32: {}", mem::size_of::<i32>());
println!("i64: {}", mem::size_of::<i64>());
println!("i128: {}", mem::size_of::<i128>());
println!("isize: {}", mem::size_of::<isize>());
println!("u8: {}", mem::size_of::<u8>());
println!("u16: {}", mem::size_of::<u16>());
println!("u32: {}", mem::size_of::<u32>());
println!("u64: {}", mem::size_of::<u64>());
println!("u128: {}", mem::size_of::<u128>());
println!("usize: {}", mem::size_of::<usize>());
println!("f32: {}", mem::size_of::<f32>());
println!("f64: {}", mem::size_of::<f64>());
println!("bool: {}", mem::size_of::<bool>());
println!("char: {}", mem::size_of::<char>());
// Выравнивание (alignment)
println!("\nВыравнивание типов:");
println!("i8: {} байт", mem::align_of::<i8>());
println!("i16: {} байт", mem::align_of::<i16>());
println!("i32: {} байт", mem::align_of::<i32>());
println!("i64: {} байт", mem::align_of::<i64>());
println!("f64: {} байт", mem::align_of::<f64>());
println!("char: {} байт", mem::align_of::<char>());
}
Лучшие практики работы с типами
1. Выбор подходящих типов
fn main() {
// Для индексов массивов используйте usize
let items = vec![1, 2, 3, 4, 5];
for index in 0..items.len() { // len() возвращает usize
println!("Элемент {}: {}", index, items[index]);
}
// Для счётчиков обычно достаточно u32
let mut counter: u32 = 0;
for _ in 0..1000 {
counter += 1;
}
// Для денежных расчётов избегайте f64
// Лучше использовать целые числа (копейки вместо рублей)
let price_in_cents: u64 = 1599; // 15.99 рублей
let quantity: u32 = 3;
let total_cents = price_in_cents * (quantity as u64);
println!("Итого: {:.2} руб.", total_cents as f64 / 100.0);
}
2. Явное указание типов когда нужно
fn main() {
// Когда компилятор не может вывести тип
let numbers: Vec<i32> = Vec::new(); // Пустой вектор
// При парсинге строк
let parsed: i32 = "42".parse().expect("Не число");
// При работе с generic функциями
let result = std::cmp::max(1u8, 2u8); // Указываем тип через суффикс
// В match выражениях для ясности
let value = 42;
match value {
x if x > 100i32 => println!("Большое число"),
_ => println!("Обычное число"),
}
}
3. Избегание переполнений
fn main() {
// Проверяющие операции
let a: u8 = 200;
let b: u8 = 100;
match a.checked_add(b) {
Some(sum) => println!("Сумма: {}", sum),
None => println!("Переполнение при сложении!"),
}
// Насыщающие операции
let saturated_sum = a.saturating_add(b);
println!("Насыщающая сумма: {}", saturated_sum); // 255 (максимум u8)
// Обёртывающие операции (когда переполнение ожидается)
let wrapping_sum = a.wrapping_add(b);
println!("Обёртывающая сумма: {}", wrapping_sum); // 44 (300 - 256)
}
Заключение
В этой главе мы детально изучили скалярные типы данных Rust:
✅ Целые числа — знаковые и беззнаковые, разных размеров ✅ Числа с плавающей точкой — f32 и f64, особенности точности ✅ Булевы значения — логические операции и преобразования ✅ Символы — Unicode-поддержка и методы работы с char ✅ Преобразования типов — безопасные и небезопасные приведения ✅ Размеры и выравнивание — оптимизация использования памяти ✅ Лучшие практики — правильный выбор типов для задач
Понимание типов данных критически важно для написания эффективных и безопасных программ на Rust. Статическая типизация помогает избежать многих ошибок на этапе компиляции.
В следующей главе: "Константы и статические переменные" — мы углубимся в изучение констант, статических переменных и их отличий от обычных переменных.
Практические задания
-
Создайте программу конвертер единиц, которая преобразует температуру между Цельсием, Фаренгейтом и Кельвином, используя подходящие типы с плавающей точкой
-
Напишите анализатор паролей, который проверяет пароль на наличие различных типов символов (буквы, цифры, специальные символы) и возвращает булевы значения
-
Реализуйте безопасный калькулятор, который использует проверяющие арифметические операции и обрабатывает переполнения
-
Создайте программу для работы с цветами, которая использует u8 для компонентов RGB и демонстрирует преобразования между разными представлениями цветов
Вопросы для самопроверки
- В чём разница между i32 и isize?
- Почему char занимает 4 байта в Rust?
- Когда следует использовать f32 вместо f64?
- Как безопасно преобразовать i64 в i32?
- Что происходит при переполнении в debug и release режимах?
Полезные ссылки
- The Rust Book - Data Types
- Rust Reference - Types
- Rust by Example - Primitives
- IEEE 754 Standard — стандарт чисел с плавающей точкой